/*
 * jQuery autobox plugin 0.5.2
 *
 * Copyright (c) 2008 Big Red Switch
 * http://www.bigredswitch.com/blog/2008/10/jquery-dynamic-textboxlist/
 *
 * Licensed under the BSD:
 *  http://www.opensource.org/licenses/bsd-license.php
 *
 * This is a mod port to jQuery of Guillermo Rauch's mootools script:
 *  http://devthought.com/textboxlist-fancy-facebook-like-dynamic-inputs/
 * Thanks Guillermo!
 *
 * Caret position method: Diego Perini: http://javascript.nwbox.com/cursor_position/cursor.js
 *
 * Plugin dev pattern:
 *  http://www.learningjquery.com/2007/10/a-plugin-development-pattern
 *
 * Changes:
 * 0.5.2 : called update to change input submit value
 *         changed separtor string to ',' from '###'
 * 0.5.1 : changed name to autobox
 * 0.5.0 : initial upload
 *
 */
(function($) {

  function LOG(obj) {
    if (window.console && window.console.log) {
      window.console.log(obj);
    }
    else {
      var console = $('#console');
      if (console) {
        console.append(obj);
        console.append('<br/>\n');
      }
    }
  }

  // resizable textbox

  $.fn.resizableTextbox = function(el, options) {
    var $opts = $.extend({
        min:    5,
        max:    500,
        step:   7
      }, options);

    var $width = el.attr('offsetWidth');

    el
      .bind('keydown', function(e) {
        $(this).data('rt-value', this.value.length);
      })
      .bind('keyup', function(e) {
        var $this = $(this);
        var newsize = $opts.step * $this.val().length;
        if (newsize <= $opts.min) {
          newsize = $width;
        }
        if (!($this.val().length == $this.data('rt-value') ||
              newsize <= $opts.min ||
              newsize >= $opts.max)) {
          $this.width(newsize);
        }
       });
  };


  $.fn.autobox = function(options) {

    return this.each(function() {

      // member vars

      var $count = 0;
      var $current = undefined; // currently focused element
      var $bits = { };
      var $opts = {
        className : 'bit'
      };
      var $maininput = undefined;
      var $element = undefined; // this element
      // auto
      var $autoholder = undefined;
      var $autoresults = undefined;
      var $resultsshown = undefined;
      var $data = [];
      var $dosearch = false;
      var $autocurrent = undefined;
      var $autoenter = false;
      var $lastinput = undefined;


      // member methods

      function setEvents() {
        $(document)
          .bind($.browser.msie ? 'keydown' : 'keypress', function(e) {
            if ($current &&
                $current.data('type') == 'box' && e.keyCode == 8) { // 8=BACKSPACE
              e.stopPropagation();
            }
          });

        $(document)
          .bind('keyup', function(e) {
            e.stopPropagation();
            if (!$current) {
              return;
            }
            switch(e.keyCode) {
              case 37: return move('left'); // left arrow
              case 39: return move('right');// right arrow
              case 46: case 8:
                if ($current.data('type') == 'box') {
                  return dispose($current);
                }
                break;
            }
          });

        $(document)
          .bind('click', function(e) {
            $(document).trigger('blur');
          });
      }

      function update() {
        var buf = '';
        for (var key in $bits) {
          if (buf.length > 0) {
            buf += $opts.separator;
          }
          buf += $bits[key];
        }
        $element.val(buf);
        return $element;
      }

      function add(text, html) {
        var id = $opts.className + '-' + $count++;
        var el =
          createBox(text ? text : html, $maininput)
            .attr('id', id)
            .bind('click', function(e) {
              e.stopPropagation();
              focus(el);
            })
            .insertBefore($maininput);
        $bits[id] = text;
        update();
      }

      function dispose(el) {
        delete $bits[el.attr('id')];
        update();
        if ($current == el) {
          focus(el.next());
        }
        if (el.data('type') == 'box') {
          el.trigger('boxDispose');
        }
        el.remove();
        return this;
      }

      /**
       * Add focus class to current.
       * If the current is input, then focus it.
       * Set current to the element.
       */
      function focus(el) {
        if ($current && $current == el) {
          return el;
        }

        blur();

        el.addClass($opts.className + '-' + el.data('type') + '-focus');

        if (el.data('type') == 'input') {
          var input = el.data('input');
          input.trigger('inputFocus');
          setTimeout(function() { input[0].focus(); }, 200);
        }
        else {
          el.trigger('boxFocus');
        }

        $current = el;
        return el;
      }

      /**
       * If current is input, then blur the control.
       * Remove focus class from current and set current to undefined..
       */
      function blur() {
        if (!$current)
          return this;

        if ($current.data('type') == 'input') {
          var input = $current.data('input');
          setTimeout(function() { input[0].blur(); }, 200);//causes recursive call on blur() on some browsers
          input.trigger('inputBlur');
        }
        else {
          $current.trigger('boxBlur');
        }

        $current.removeClass($opts.className + '-' + $current.data('type') + '-focus');
        if ($current) { // if called recursively (see above), then $current==null
        }
        $current = undefined;
        return this;
      }

      function createBox(text, focusElementOnRemove) {
        var li = $('<li>'+text+'</li>')
                 .addClass($opts.className + '-box')
                 .data('type', 'box');
        if (!$opts.noRemove) {
          li
            .bind('mouseenter', function(e) {
              li.addClass('bit-hover');
            })
            .bind('mouseleave', function(e) {
              li.removeClass('bit-hover');
            })
            .append($('<a href="#" class="closebutton"></a>')
                  .bind('click', function(e) {
                    e.stopPropagation();
                    if (!$current) {
                      focus(focusElementOnRemove);
                    }
                    dispose(li);
                    return false;
                  }))
            .data('text', text);
        }
        bindCustomEvents(li);
        return li;
      }

      function createInput() {
        var li = $('<li class="' + $opts.className + '-input"></li>');

        var input = $('<input type="text"></input>');
        input
          .bind('click', function(e) {
            e.stopPropagation();
          })
          .bind('focus', function(e) {
              //focus(li);
          })
          .bind('blur', function(e) {
            if ($current == li) {
              //$current = undefined;
              blur();
            }
          })
          .bind('keydown', function(e) {
            $(this)
              .data('lastValue', this.value)
              .data('lastCaret', getCaretPosition(input));
            if ($autoholder) {
              $dosearch = false;
              switch (e.keyCode) {
                case 38 : return autoMove('up');
                case 40 : return autoMove('down');
                case 13 : // enter
                  if ($resultsshown && $autocurrent && $autocurrent.data('result')) {
                    autoAdd($autocurrent);
                    $autocurrent = false;
                    $autoenter = true;
                  }
                  else if (this.value) {
                    add(this.value);
                    this.value = '';
                  }
                  break;
                case 27 : // esc
                  if (!$resultsshown && $current && $current.data('input')) {
                    setTimeout(function() { $current.data('input').val('') }, 0);
                  }
                  else {
                    e.stopPropagation();
                    autoHide();
                    return false;//skip IE default behavior (clear field)
                  }
                  break;
                default:
                  $dosearch = true;
              }

              if ($autoenter && $.browser.msie) {
                e.stopPropagation();
                $autoenter = false;
              }

              return e.keyCode != 13; // skip IE default behavior (beeps)
            }
          })
          .bind('keypress', function(e) {
            if ($autoenter && !$.browser.msie) {
              e.stopPropagation();
              $autoenter = false;
            }
          })
          .bind('keyup', function(e) {
            if ($autoholder) {
              if ($dosearch) {
                autoShow(input.val());
              }
            }
          });

        bindCustomEvents(input);

        return li.data('type', 'input')
                 .data('input', input)
                 .append(input);
      }

      function makeResizable(li) {
        var el = li.data('input');
        el.data('resizable', $.fn.resizableTextbox(el, $.extend($opts.resizable, { min: el.attr('offsetWidth'), max: $element.width() })));
        return this;
      }

      function checkInput() {
        var input = $current.data('input');
        return !input.data('lastValue') ||
               (getCaretPosition(input) === 0 && input.data('lastCaret') === 0);
      }

      function move(direction) {
        var el = direction == 'left' ? $current.prev() : $current.next();
        if (el.length > 0 && (!$current.data('input') || checkInput() || direction == 'right')) {
          focus(el);
        }
        return this;
      }

      function isObjEmpty(obj) {
        for (var i in obj) {
          return false;
        }
        return true;
      }

      function getCaretPosition(input) {
        if (input.createTextRange) {
          var r = document.selection.createRange().duplicate();
          r.moveEnd('character', input.val().length);
          if (r.text === '') {
            return input.val().length;
          }
          return input.val().lastIndexOf(r.text);
        }
        else {
          return input.attr('selectionStart');
        }
      }

      var specials = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\' ];
      var specialsRE = new RegExp('(\\' + specials.join('|\\') + ')', 'g');

      function escRE(text) {
        return text.replace(specialsRE, '\\$1');
      }

      function autoShow(search) {
        $autoholder.show();
        $autoholder.children().hide();

        if (!search || !$.trim(search) ||
            (!search.length || search.length < $opts.autocomplete.minchars)) {
          $autoholder.find('.default').show();
          $resultsshown = false;
        }
        else {
          $resultsshown = true;
          $autoresults.show().empty();
          var j = 0;

          search = escRE(search);

          $.each(
              $.grep($data, function(str, idx) {
                return idx < $opts.autocomplete.maxresults ?
                       (str ? (new RegExp(search, 'i')).test(str) : false) : false;
              }),
              function(item) {
                var el =
                  $('<li></li>')
                  .bind('mouseenter', function(e) {
                    autoFocus($(this));
                  })
                  .bind('click', function(e) {
                    e.stopPropagation();
                    autoAdd($(this));
                  })
                  .html(autoHighlight(this, search))
                  .data('result', this);
                $autoresults.append(el);
                if (j == 0) {
                  ++j;
                  autoFocus(el);
                }
              }
          );
        }
        return this;
      }

      function autoHighlight(html, highlight) {
        return html.replace(new RegExp(highlight, 'gi'), function(match) {
          return '<em>' + match + '</em>';
        });
      }

      function autoHide() {
        $resultsshown = false;
        $autoholder.hide();
      }

      function autoFocus(el) {
        if (el) {
          if ($autocurrent) {
            $autocurrent.removeClass('auto-focus');
          }
          $autocurrent = el.addClass('auto-focus');
        }
      }

      function autoMove(direction) {
        if ($resultsshown && $autocurrent) {
          if (direction == 'up') {
            if ($autocurrent.prev().length > 0)
              autoFocus($autocurrent.prev());
          }
          else {
            if ($autocurrent.next().length > 0)
              autoFocus($autocurrent.next());
          }
        }
        return this;
      }

      function autoFeed(text) {
        for (var i = 0; i < $data.length; ++i) {
          if ($data[i] == text)
            return this;
        }
        $data.push(text);
        return this;
      }

      function autoAdd(el) {
        if (el && el.data('result')) {
          add(el.data('result'));
          for (var i = 0; i < $data.length; ++i) {
            if ($data[i] == el.data('result')) {
              delete $data[i];
              break;
            }
          }

          autoHide();

          $maininput.data('input').val('');
          focus($maininput);
        }
      }

      function bindCustomEvents(el) {
        // bind custom events, events are in options with ^on[A-Z]\w* : function
        for (var k in $opts) {
          if (k.search(/^on[A-Z]/) == 0 && typeof $opts[k] == 'function') {
            var ev = k.substr(2,1).toLowerCase() + k.substr(3);
            el.bind(ev, $opts[k]);
          }
        }
      }

      // body
      $(this).parent('li').addClass('autobox-input-text');
      $(this).parent

      $opts = $.extend({
          // auto
          onBoxDispose : function(item) {
            if ($autoholder) {
              autoFeed($(this).data('text'));
            }
          },
          onInputFocus : function() {
            if ($autoholder) {
              autoShow();
            }
          },
          onInputBlur : function(e) {
            if ($autoholder) {
              $lastinput = $(this);
              // we need to call autoHide _after_ the click is called, clever hack?
              setTimeout(function() { autoHide(); }, 200);
            }
          }
        },
        $.fn.autobox.defaults,
        options);

      // support metadata plugin (element specific options)
      var o = $.meta ? $.extend({ }, $opts, $(this).data()) : $opts;

      $element = $(this);
      $(this).hide();

      $maininput = createInput();
      $maininput
        .find('input')
        .addClass('maininput');

      var holder =
        $('<ul></ul>')
          .addClass('autobox-holder')
          .css('width',$opts.width)
          .bind('click', function(e) {
              e.stopPropagation();
              if ($current != $maininput) {
                focus($maininput);
              }
              return false;
            })
            .append($maininput)
            .insertBefore($(this));

      $autoholder = $('#' + this.id + '-auto');
      if ($autoholder) {
        $autoholder
          .addClass('autobox-auto')
          .css('opacity', $opts.autocomplete.opacity)
          .hide();
        $autoresults = $autoholder.children('ul');

        $autoresults.children('li').each(function() {
          add($(this).html());
        });
      }

      makeResizable($maininput);
      setEvents();

      // public methods are added to this
      this.add = add;
      this.autoFeed = autoFeed;
    });

  };

  $.fn.autobox.defaults = {
    resizable :   { },
    className :   'bit',
    separator :   '|',
    startInput :  true,
    hideEmpty :   true,
    noRemove :    false,        // set to true if no remove boxes
    width :       '396px',
    // autocompleter box
    autocomplete : {
      opacity :         1,
      maxresults :      15,
      minchars :        1
    }
  };


})(jQuery);
